/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: service_property.h
 *
 * Purpose: property base class define for service side
 *
 * Developer:
 *   wen.gu , 2021-09-09
 *
 * TODO:
 *
 ***************************************************************************/

/******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#ifndef __ICPP_COM_SERVICE_PROPERTY_H__
#define __ICPP_COM_SERVICE_PROPERTY_H__
#include <functional>
#include <vector>
#include <memory>

#include "icpp/com/types.h"
#include "icpp/com/message.h"
#include "icpp/com/response_builder.h"
#include "icpp/com/template_utils.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/


/******************************************************************************
 **    TYPE DEFINITIONS
 ******************************************************************************/
namespace icpp
{
namespace com
{


/******************************************************************************
 **    CLASSES/FUNCTIONS DEFINITIONS
 ******************************************************************************/
template<class ProviderPtr, PropertyId property_id, typename PropertyType, typename = typename std::enable_if<!std::is_same<PropertyType, void>::value>::type >
class COM_CLASS ServiceProperty final
{
public:
    using PropertyGetHandler = std::function<Response<PropertyType>()>;
    using PropertySetHandler = std::function<Response<PropertyType>(const PropertyType&)>;
private:
    ProviderPtr provider_ptr_;
    PropertyType property_val_;
    Serializer::SerializerPtr serializer_ptr_;
    Deserializer::DeserializerPtr deserializer_ptr_;
    PropertyGetHandler property_getter_ = nullptr;
    PropertySetHandler property_setter_ = nullptr;
public:
    ServiceProperty(ProviderPtr provider_ptr)
    :provider_ptr_(provider_ptr),
    serializer_ptr_(provider_ptr->newSerializer()),
    deserializer_ptr_(provider_ptr->newDeserializer(nullptr))
    {
        /** todo something */
        /** register setter */
        provider_ptr_->registerMessageHandler(MessageType::kPropertySet, properpty_id, [this](Message::MessagePtr msg_ptr)
        {
            if (msg_ptr->payload())
            {
                this->deserializer_ptr_->setNewPayload(msg_ptr->payload());                    
                msg_ptr->set_payload(nullptr);

                PropertyType val;
                bool ret = this->deserializer_ptr_->deserialize(val);

                if (ret)
                {
                    if (this->property_setter_)  
                    {
                        Response<PropertyType> res = this->property_setter_(val);
                        Result<PropertyType> res_val = res.getResult();

                        if (res_val.hasValue())
                        {
                            this->serializer_ptr_->reset();
                            ret = this->serializer_ptr_->serialize(res_val.value());
                            if (ret)
                            {
                                msg_ptr->set_payload(this->serializer_ptr_->payload());
                                this->update(res_val.value());
                            }
                            else
                            {
                                msg_ptr->set_error_code(core::IcppErrc::SerializeFailed);
                            }
                        }
                        else
                        {
                            msg_ptr->set_error_code(res_val->error());
                        }                          
                    } 
                    else
                    {
                        this->serializer_ptr_->reset();
                        ret = this->serializer_ptr_->serialize(val);
                        if (ret)
                        {
                            msg_ptr->set_payload(this->serializer_ptr_->payload());
                            this->update(val);
                        }
                        else
                        {
                            msg_ptr->set_error_code(core::IcppErrc::SerializeFailed);
                        }
                    }                                       
                }
                else
                {
                    msg_ptr->set_error_code(core::IcppErrc::DeserializeFailed);
                }                  
            }
            else
            {
                msg_ptr->set_error_code(core::IcppErrc::Undefined);
            }

            msg_ptr->set_type(MessageType::kPropertySetAck);
            
            this->provider_ptr_->sendMessage(msg_ptr);
        });  

        /** register getter */
        provider_ptr_->registerMessageHandler(MessageType::kPropertyGet, properpty_id, [this](Message::MessagePtr msg_ptr)
        {
            if (this->property_getter_)
            {
                Response<PropertyType> res = this->property_getter_();
                Result<PropertyType> res_val = res.getResult();                

                if (res_val.hasValue())
                {
                    this->serializer_ptr_->reset();
                    bool ret = this->serializer_ptr_->serialize(res_val.value());
                    if (ret)
                    {
                        msg_ptr->set_payload(this->serializer_ptr_->payload());
                    }
                    else
                    {
                        msg_ptr->set_error_code(core::IcppErrc::SerializeFailed);
                    }
                }
                else
                {
                    msg_ptr->set_error_code(res_val->error());
                }                 
            }
            else
            {
                bool ret = this->serializer_ptr_->serialize(this->property_val_);
                if (ret)
                {
                    msg_ptr->set_payload(this->serializer_ptr_->payload());
                }
                else
                {
                    msg_ptr->set_error_code(core::IcppErrc::SerializeFailed);
                }                
            }


            msg_ptr->set_type(MessageType::kPropertyGetAck);
            
            this->provider_ptr_->sendMessage(msg_ptr);                                                       
        });       
    }

public:
    /**
     * Update equals the send method of the event. This triggers the
     * transmission of the notify (if configured) to the subscribed clients.
     *
     * In case of a configured Getter, this has to be called at least once to set the initial value.
     */
    void update(const PropertyType& data)
    {
        property_val_ = data;
        Message::MessagePtr msg_ptr = provider_ptr_->newMessage(MessageType::kPropertyNotify, event_id, nullptr);

        if (msg_ptr == nullptr)
        {
            return ;
        }

        serializer_ptr_->reset();
        bool ret = serializer_ptr_->serialize(data);

        if (ret)
        {
            msg_ptr->set_payload(serializer_ptr_->payload());
        }
        else
        {
            msg_ptr->set_error_code(core::IcppErrc::SerializeFailed);
        }

        provider_ptr_->sendMessage(msg_ptr);
    }

    /**
     * Registering a SetHandler is mandatory, if the property supports it.
     * The handler gets the data the sender requested to be set.
     * It has to validate the settings and perform an update of its internal data. 
     * The new value of the property should than be set in the future.
     *
     * The returned value is sent to the requester and is sent via notification to all subscribed entities.
     */
    void registerSetHandler(PropertySetHandler handler)
    {
        property_setter_ = handler;
    }

    /**
     * Registering a GetHandler is optional. If registered the function is called whenever a get request is received.
     *
     * If no Getter is registered icpp::com is responsible for responding to the request using the last value set by update.
     *
     * This implicitly requires at least one call to update after initialization of the Service, before the service
     * is offered. This is up to the implementer of the service The get handler shall return a future.
     */
    void registerGetHanlder(PropertyGetHandler handler)
    {
        property_getter_ = handler;
    }

    bool hasSetter() const
    {
        return property_setter_ == nullptr;
    }

    bool hasGetter() const
    {
        return property_getter_ == nullptr;
    }

};


} /** namespace com */
} /** namespace icpp */

#endif /** !__ICPP_COM_SERVICE_PROPERTY_H__ */

